#pragma once

#include <stx/core/fs.hpp>
#include <stx/ext/simdjson.hpp>

inline namespace stx
{
using JsonParser__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::parser;
using JsonDocument__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::document;
using JsonValueType__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::json_type;
using JsonNumber__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::number;
using JsonNumberType__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::number_type;
using JsonValue__ = simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::value;
using JsonField__ = simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::field;
using JsonObject__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object;
using JsonArray__ = simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array;
using JsonObjectIterator__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::object_iterator;
using JsonArrayIterator__ =
    simdjson::SIMDJSON_BUILTIN_IMPLEMENTATION::ondemand::array_iterator;

constexpr auto C_SIMDJSON_PADDING_SIZE = simdjson::SIMDJSON_PADDING;

enum class JsonType
{
    K_NULL,
    K_BOOL,
    K_INT64,
    K_DOUBLE,
    K_STRING,
    K_OBJECT,
    K_ARRAY,
};

class JsonValue final
{
    mutable std::optional<JsonValue__> opt_value_;

public:
    JsonValue() noexcept = default;
    explicit JsonValue(JsonValue__ const& value);

    [[nodiscard]] bool isNull() const;
    [[nodiscard]] bool isBool() const;
    [[nodiscard]] bool isNumber() const;
    [[nodiscard]] bool isInt64() const;
    [[nodiscard]] bool isDouble() const;
    [[nodiscard]] bool isString() const;
    [[nodiscard]] bool isObject() const;
    [[nodiscard]] bool isArray() const;

    [[nodiscard]] bool             getBool() const;
    [[nodiscard]] Number           getNumber() const;
    [[nodiscard]] std::int64_t     getInt64() const;
    [[nodiscard]] double           getDouble() const;
    [[nodiscard]] std::string_view getString() const;

    [[nodiscard]] JsonType getType() const;
    [[nodiscard]] std::optional<JsonValue>
                                     getValue(std::string_view const jp) const;
    [[nodiscard]] JsonValue__ const& get() const;
};

struct JsonEntry final
{
    std::string_view key;
    JsonValue        value;
};

class JsonObjectIterator final
{
    struct Context_ final
    {
        JsonEntry            field;
        JsonObjectIterator__ pos;
    };

    std::optional<Context_> opt_ctx_;

public:
    using difference_type   = std::ptrdiff_t;
    using value_type        = JsonEntry;
    using pointer           = value_type*;
    using reference         = value_type&;
    using iterator_category = std::forward_iterator_tag;

    JsonObjectIterator() noexcept = default;

    explicit JsonObjectIterator(JsonObjectIterator__ const& pos);

    [[nodiscard]] bool operator==(JsonObjectIterator const& other) const;

    [[nodiscard]] value_type const& operator*() const;
    [[nodiscard]] value_type const* operator->() const;

    JsonObjectIterator& operator++();
    JsonObjectIterator  operator++(int);

private:
    void setField_();
};

class JsonObject final
{
    mutable std::optional<JsonObject__> opt_obj_;

public:
    JsonObject() noexcept = default;
    explicit JsonObject(JsonObject__ const& obj);

    [[nodiscard]] JsonObjectIterator begin() const;
    [[nodiscard]] JsonObjectIterator end() const;

    [[nodiscard]] std::optional<JsonValue>
    operator[](std::string_view const key) const;
    [[nodiscard]] std::optional<JsonValue>
    getValue(std::string_view const jp) const;
};

class JsonArrayIterator final
{
    struct Context_ final
    {
        JsonValue           value;
        JsonArrayIterator__ pos;
    };

    std::optional<Context_> opt_ctx_;

public:
    using difference_type   = std::ptrdiff_t;
    using value_type        = JsonValue;
    using pointer           = value_type*;
    using reference         = value_type&;
    using iterator_category = std::forward_iterator_tag;

    JsonArrayIterator() noexcept = default;
    JsonArrayIterator(JsonArrayIterator__ const& pos);

    [[nodiscard]] bool operator==(JsonArrayIterator const& other) const;

    [[nodiscard]] value_type const& operator*() const;
    [[nodiscard]] value_type const* operator->() const;

    JsonArrayIterator& operator++();
    JsonArrayIterator  operator++(int);

private:
    void setField_();
};

class JsonArray final
{
    mutable std::optional<JsonArray__> opt_arr_;

public:
    JsonArray() noexcept = default;
    explicit JsonArray(JsonArray__ const& arr);

    [[nodiscard]] JsonArrayIterator begin() const;
    [[nodiscard]] JsonArrayIterator end() const;

    [[nodiscard]] std::optional<JsonValue>
    operator[](std::size_t const idx) const;
    [[nodiscard]] std::optional<JsonValue>
    getValue(std::string_view const jp) const;
};

[[nodiscard]] JsonObject getJsonObject(JsonValue const& value);
[[nodiscard]] JsonArray  getJsonArray(JsonValue const& value);

class JsonParser final
{
    std::string    raw_;
    JsonParser__   parser_;
    JsonDocument__ doc_;

public:
    JsonParser();
    explicit JsonParser(std::size_t const init_capacity);

    NON_COPYABLE(JsonParser);
    NON_MOVABLE(JsonParser);

    [[nodiscard]] bool parse(std::string_view const sv_jsn);
    [[nodiscard]] bool parse(void const* const data,
                             std::size_t const size,
                             std::size_t const capacity);

    [[nodiscard]] std::optional<JsonValue>
    getValue(std::string_view const jp) const;
};

[[nodiscard]] bool isValidUtf8String(std::string_view const sv);
[[nodiscard]] bool isValidJsonString(std::string_view const sv);

class JsonBuilderNode final
{
private:
    class Value_ final
    {
        std::unique_ptr<JsonBuilderNode> up_value_;

    public:
        Value_();
        explicit Value_(std::unique_ptr<JsonBuilderNode> up_value);

        Value_(Value_ const& other);
        DEFINE_COPY_SEMANTICS(Value_, Value_ const&);

        DEFINE_MOVE_SEMANTICS(Value_, {
            this->up_value_ = std::move(other.up_value_);
        });

        [[nodiscard]] JsonBuilderNode const& get() const;
        [[nodiscard]] JsonBuilderNode&       get();
    };

    using Key_    = std::string;
    using Object_ = Map<Key_, Value_>;
    using Array_  = std::deque<Value_>;
    using Data_   = std::variant<std::monostate,
                               bool,
                               std::int64_t,
                               double,
                               std::string,
                               Object_,
                               Array_>;

private:
    Data_ data_;

public:
    JsonBuilderNode() noexcept = default;

    // NOLINTNEXTLINE
    JsonBuilderNode(std::nullptr_t const) noexcept;
    DEFINE_COPY_SEMANTICS(JsonBuilderNode, std::nullptr_t const);

    // NOLINTNEXTLINE
    JsonBuilderNode(bool const b) noexcept;
    DEFINE_COPY_SEMANTICS(JsonBuilderNode, bool const);

    // NOLINTNEXTLINE
    JsonBuilderNode(std::string s) noexcept;
    DEFINE_COPY_SEMANTICS(JsonBuilderNode, std::string);

    // NOLINTNEXTLINE
    JsonBuilderNode(JsonValue const& jsn_value);
    DEFINE_COPY_SEMANTICS(JsonBuilderNode, JsonValue const&);

    // NOLINTNEXTLINE
    JsonBuilderNode(JsonObject const& jsn_obj);
    DEFINE_COPY_SEMANTICS(JsonBuilderNode, JsonObject const&);

    // NOLINTNEXTLINE
    JsonBuilderNode(JsonArray const& jsn_arr);
    DEFINE_COPY_SEMANTICS(JsonBuilderNode, JsonArray const&);

    template<IsNumberLike T>
    requires EXPR(!IsIdentical<T, bool>)
    JsonBuilderNode(T const n) noexcept // NOLINT
    {
        using T_ = decltype(n);

        if constexpr (IsIntegral<T_>)
        {
            if constexpr (IsIdentical<T_, bool>)
            {
                this->data_ = n;
            }
            else
            {
                this->data_ = static_cast<std::int64_t>(n);
            }
        }
        else if constexpr (IsFloatingPoint<T_>)
        {
            this->data_ = static_cast<double>(n);
        }
        else
        {
            static_assert(IsIdentical<T_, Number>);
            if (n.isInt64() || n.isUint64())
            {
                this->data_ = static_cast<std::int64_t>(n);
            }
            else
            {
                this->data_ = static_cast<double>(n);
            }
        }
    }

    DEFINE_COPY_SEMANTICS(JsonBuilderNode, IsArithmetic auto const);

    void                                erase(std::string_view const sv_key);
    [[nodiscard]] std::optional<Value_> extract(std::string_view const sv_key);

    [[nodiscard]] JsonBuilderNode& operator[](std::string s_key);
    [[nodiscard]] JsonBuilderNode& operator[](std::string_view const sv_key);

    JsonBuilderNode&          append(JsonBuilderNode elem);
    [[nodiscard]] std::string dump() const;
};
} // namespace stx